/*
 * This project provides a number of implementations for indexing and evaluating
 * <i>Continuous Top-k Queries over Text Streams</i>
 * and has been developped as part of the work described in the article:
 * 
 * Vouzoukidou, N., Amann, B., & Christophides, V. (2012, October). 
 * Processing continuous text queries featuring non-homogeneous scoring functions.
 * In Proceedings of the 21st ACM international conference on Information and 
 * knowledge management (pp. 1065-1074). ACM.
 * http://dl.acm.org/citation.cfm?id=2398404
 * 
 * This software is distributed under the GNU General Public License and as an
 * additional condition of usage, please cite the aforementioned article.
 * 
 * Copyright (C) 2012 ICS/FORTH, Greece and 
 *               2012 LIP6, France
 * 
 * Author: Vouzoukidou Nelly, email: nelly.vouzoukidou (at) gmail (dot) com
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package gr.forth.ics.continuoustopk.metrics;

/**
 * 
 * 
 * @author Vouzoukidou Nelly, email: nelly.vouzoukidou (at) gmail (dot) com
 */
public abstract class DecayFunction {
    // package private constructor
    DecayFunction() {}
    
    /**
     * Returns the score {@code scoreBefore} afte decay has been applied on it. 
     * The result is the score after {@code delta} time has passed.
     * 
     * @param score the score at some time instant <i>t</i>
     * @param delta the time that passed since time instant <i>t</i>
     * @return
     */
    public abstract double decay(double scoreBefore, long delta);

    /**
     * Returns the reverse (or backwards) decay of the score given as argument. 
     * That is, if score is {@code scoreAfter} right now, what would it be 
     * {@code delta} time before?
     * 
     * @param scoreAfter the score whose inverse decay is to be found
     * @param delta the time difference
     * @return 
     */
    public abstract double inverseDecay(double scoreAfter, long delta);

    public abstract DecayFunctionType type();
    
    public static DecayFunction noDecay() {
        return NoDecay.instance;
    }

    public static DecayFunction slidingWindow(long windowSize) {
        return new SlidingWindow(windowSize);
    }

    public static DecayFunction linearDecay(double stepDecay) {
        return new LinearDecay(stepDecay);
    }

    /**
     * The following function is used for decay: score * exp ^ (- a * delta)
     * @param a
     * @return
     */
    public static DecayFunction exponentialDecay(double a) {
        return new ExponentialDecay(a);
    }

    public enum DecayFunctionType {
        noDecay,
        slidingWindow,
        linear,
        exponential
    }
}

// <editor-fold defaultstate="collapsed" desc="no decay">
class NoDecay extends DecayFunction {
    private NoDecay() {}

    final static NoDecay instance = new NoDecay();

    @Override
    public double decay(double scoreBefore, long delta) {
        return scoreBefore;
    }
    
    @Override
    public double inverseDecay(double scoreAfter, long delta) {
        return scoreAfter;
    }

    @Override
    public DecayFunctionType type() {
        return DecayFunctionType.noDecay;
    }

    @Override
    public String toString() {
        return "No decay";
    }
}
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="sliding window">
class SlidingWindow extends DecayFunction {
    private long windowSize;

    SlidingWindow(long windowSize) {
        this.windowSize = windowSize;
    }

    @Override
    public double decay(double scoreBefore, long delta) {
        return delta < windowSize ? scoreBefore : 0;
    }
    
    @Override
    public double inverseDecay(double scoreAfter, long delta) {
        throw new UnsupportedOperationException("Cannot predict previous value for time window decay (No information on publication time)");
    }

    @Override
    public DecayFunctionType type() {
        return DecayFunctionType.slidingWindow;
    }

    @Override
    public String toString() {
        return "Sliding window. windowSize=" + windowSize;
    }
}
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="linear decay">
class LinearDecay extends DecayFunction {
    private double step;
    // step: decrease for delta = 1
    LinearDecay(double step) {
        this.step = step;
    }

    @Override
    public double decay(double scoreBefore, long delta) {
        return Math.max(0, scoreBefore - delta * step);
    }
    
    @Override
    public double inverseDecay(double scoreAfter, long delta) {
        return scoreAfter + delta * step;
    }

    @Override
    public DecayFunctionType type() {
        return DecayFunctionType.linear;
    }

    @Override
    public String toString() {
        return "Linear decay. step=" + step;
    }
}
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="exponential decay">
/**
 * score * e^-(a * delta)
 */
class ExponentialDecay extends DecayFunction {
    private double a;

    ExponentialDecay(double a) {
        this.a = a;
    }

    @Override
    public double decay(double scoreBefore, long delta) {
        // javadocs on expm1:
        // "Note that for values of x near 0, the exact sum of expm1(x) + 1 is
        // much closer to the true result of ex than exp(x)"
        return Math.max(0, scoreBefore * (Math.expm1(- a * delta) + 1));
    }
    
    @Override
    public double inverseDecay(double scoreAfter, long delta) {
        return scoreAfter * (Math.expm1(a * delta) + 1);
    }

    @Override
    public DecayFunctionType type() {
        return DecayFunctionType.exponential;
    }

    @Override
    public String toString() {
        return "Exponential decay. exponent=" + a;
    }
}
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="geometrical series decay">
//not implemented yet
// </editor-fold>
